
// ===============================================================================================================
// -*- C++ -*-
//
// FireEffect.cpp - GPU Fire effect.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#include <Common.hpp>
#include <Camera.hpp>
#include <Renderer.hpp>
#include <FireEffect.hpp>

// =========================================================
// FireEffect Class Implementation
// =========================================================

// Shared shader instance:
ShaderProgram * FireEffect::fireShader = 0;

FireEffect::FireEffect(Texture * diffuseMap, Texture * distortionMap, Texture * opacityMap, float sizeFactor, const Vec3 & position)
: size(sizeFactor), pos(position), heightAttenuationX(0.44), heightAttenuationY(0.29), distortionAmount(-0.1230, -0.0910, -0.6723)
{
	// Adjust the position:
	pos.y = pos.y + size - 0.5f;

	fireBase = diffuseMap;
	fireOpacity = opacityMap;
	fireDistortion = distortionMap;

	fireBase->AddRef();
	fireOpacity->AddRef();
	fireDistortion->AddRef();

	if (fireShader == 0)
	{
		// First time, create new shader:
		const std::string shaderFiles[2] = { "Code/GLSL/Fire.vert", "Code/GLSL/Fire.frag" };
		fireShader = ShaderProgram::Create(shaderFiles, 2);
	}
	else
	{
		fireShader->AddRef();
	}
}

void FireEffect::SetDistortionAmount(const Vec3 & amt)
{
	distortionAmount = amt;
}

void FireEffect::SetHeightAttenuation(const Vec3 & att)
{
	heightAttenuationX = att.x;
	heightAttenuationY = att.y;
	// Z unused
}

void FireEffect::Enable(void)
{
	glPushMatrix();
	glEnable(GL_BLEND); // Set propper alpha mode:
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER, 0);

	fireShader->Enable();
}

void FireEffect::Render(void) const
{
	const Camera * camPtr = Renderer::Instance()->GetCamera();

	const Vec3 eye(camPtr->GetEye());
	const Vec3 dist((eye.x - pos.x), (eye.y - pos.y), (eye.z - pos.z));

	if (dist.Length() < 900.0f) // In range ?
	{
		const Vec3 up(camPtr->GetUp());
		const Vec3 right(camPtr->GetRight());

		// Set shader params:
		fireShader->SetUniform1f("distortionAmount0", distortionAmount.x);
		fireShader->SetUniform1f("distortionAmount1", distortionAmount.y);
		fireShader->SetUniform1f("distortionAmount2", distortionAmount.z);

		fireShader->SetUniform1f("heightAttenuationX", heightAttenuationX);
		fireShader->SetUniform1f("heightAttenuationY", heightAttenuationY);

		fireShader->SetUniform1f("time", -GetElapsedSeconds());
		fireShader->SetUniform1i("fireDistortion", 2);
		fireShader->SetUniform1i("fireOpacity", 1);
		fireShader->SetUniform1i("fireBase", 0);

		// Set textures:
		fireDistortion->Bind(2);
		fireOpacity->Bind(1);
		fireBase->Bind(0);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		// Render geometry:
		glBegin(GL_QUADS);
		glTexCoord2f(0.0f, 0.0f);
		glVertex3fv(Vec3(pos.x + (right.x + up.x) * -size,
		                 pos.y + (right.y + up.y) * -size,
		                 pos.z + (right.z + up.z) * -size).v);

		glTexCoord2f(1.0f, 0.0f);
		glVertex3fv(Vec3(pos.x + (right.x - up.x) * size,
		                 pos.y + (right.y - up.y) * size,
		                 pos.z + (right.z - up.z) * size).v);

		glTexCoord2f(1.0f, 1.0f);
		glVertex3fv(Vec3(pos.x + (right.x + up.x) * size,
		                 pos.y + (right.y + up.y) * size,
		                 pos.z + (right.z + up.z) * size).v);

		glTexCoord2f(0.0f, 1.0f);
		glVertex3fv(Vec3(pos.x + (up.x - right.x) * size,
		                 pos.y + (up.y - right.y) * size,
		                 pos.z + (up.z - right.z) * size).v);
		glEnd();
	}
}

void FireEffect::Disable(void)
{
	fireShader->Disable();

	glDisable(GL_ALPHA);
	glDisable(GL_BLEND);
	glPopMatrix();
}

FireEffect::~FireEffect(void)
{
	fireBase->Release();
	fireOpacity->Release();
	fireDistortion->Release();

	if ((fireShader != 0) && (fireShader->Release() == 0))
	{
		// No longer in use
		fireShader = 0;
	}
}